// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fcntl.h>
#include <stdio.h>

#include <fbl/unique_fd.h>
#include <lib/disk-inspector/disk-inspector.h>
#include <minfs/inspector.h>
#include <zircon/status.h>

namespace {

// Processes various disk objects recursively starting from root object
// and prints values/elements of the objects.
void ProcessDiskObjects(std::unique_ptr<disk_inspector::DiskObject> obj, uint32_t num_tabs) {
  if (obj == nullptr)
    return;

  printf("\n");
  for (uint32_t i = 0; i < num_tabs; i++) {
    printf("\t");
  }

  printf("Name: %s", obj->GetName());
  size_t num_elements = obj->GetNumElements();

  // Non scalar types.
  if (num_elements != 0) {
    for (uint32_t i = 0; i < num_elements; i++) {
      std::unique_ptr<disk_inspector::DiskObject> element = obj->GetElementAt(i);
      ProcessDiskObjects(std::move(element), num_tabs + 1);
    }
    return;
  }

  const void *buffer;
  size_t size;
  obj->GetValue(&buffer, &size);

  switch (size) {
    case sizeof(uint64_t): {
      const uint64_t *val = reinterpret_cast<const uint64_t *>(buffer);
      printf(" Value:0x%lx\n", *val);
    } break;

    case sizeof(uint32_t): {
      const uint32_t *val = reinterpret_cast<const uint32_t *>(buffer);
      printf(" Value:0x%x\n", *val);
    } break;

    case sizeof(uint16_t): {
      const uint16_t *val = reinterpret_cast<const uint16_t *>(buffer);
      printf(" Value:0x%x\n", *val);
    } break;

    case sizeof(uint8_t): {
      const char *val = reinterpret_cast<const char *>(buffer);
      printf(" Value:%c\n", *val);
    } break;

    default:
      ZX_ASSERT_MSG(false, "Unknown object size: %lu", size);
  }
}

int Inspect(fbl::unique_fd fd) {
  minfs::Inspector inspector = minfs::Inspector(std::move(fd));
  std::unique_ptr<disk_inspector::DiskObject> root;

  if (inspector.GetRoot(&root) == ZX_OK) {
    ProcessDiskObjects(std::move(root), 0);
    printf("\n");
    return 0;
  }
  fprintf(stderr, "ERROR: GetRoot failed\n");
  return -1;
}

}  // namespace

int main(int argc, char **argv) {
  if (argc < 2) {
    fprintf(stderr, "usage: %s <device path>\n", argv[0]);
    return -1;
  }

  fbl::unique_fd fd(open(argv[1], O_RDONLY));
  if (fd.get() < 0) {
    fprintf(stderr, "ERROR: Failed to open device: %d\n", fd.get());
    return -1;
  }
  return Inspect(std::move(fd));
}
